(function (){
/*
    ST - Start
    CM - Comma
    NE - Non-Escaped Field
    ES - Escaped Filed Start
    ET - Escaped Filed Text
    EQ - Escaped Filed Double Quote
    EE - Escaped Filed Escaped Double Quote
    EC - Escaped Filed Comma
    ER - Escaped Filed CR
    EF - Escaped Filed LF
    CR - CR
    LF - LF
    FN - Finish

        ST CM NE ES ET EC ER EF EQ EE CR LF FN
    ST  -  C  T  Q  -  -  -  -  -  -  R  f  E
    CM  -  C  T  Q  -  -  -  -  -  -  R  f  E
    NE  -  C  T  -  -  -  -  -  -  -  R  f  E
    ES  -  -  -  -  T  C  R  F  Q  -  -  -  -
    ET  -  -  -  -  T  C  R  F  Q  -  -  -  -
    EC  -  -  -  -  T  C  R  F  Q  -  -  -  -
    ER  -  -  -  -  T  C  R  F  Q  -  -  -  -
    EF  -  -  -  -  T  C  R  F  Q  -  -  -  -
    EQ  -  C  -  -  -  -  -  -  -  Q  R  f  E
    EE  -  -  -  -  T  C  R  F  Q  -  -  -  -
    CR  -  -  -  -  -  -  -  -  -  -  -  F  -
    LF  -  C  T  Q  -  -  -  -  -  -  R  f  E
    FN  -  -  -  -  -  -  -  -  -  -  -  -  -

*/

    var stateNames = "ST CM NE ES ET EC ER EF EQ EE CR LF FN XX".split(" ");
                    //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12,13

    var max = 1024*150000;

    function parseBuffer(ds,buffer,SCB){
        if(ds.buffer){
        }else{
            ds.buffer = buffer;
            ds.pos = 0; 
            ds.state = 0; 
            ds.prev = 0; 
            ds.prevState = 0; 
            ds.prevPos = 0; 
            (SCB&&SCB(ds,ds.state,ds.pos,ds.pos));
        }
        
        ds.S.lastIndex = 0;
        
        ds.r = ds.S.exec(ds.buffer);

        if(null != ds.r && ds.r.index != ds.pos){
            ds.state = 13;
            (SCB&&SCB(ds,ds.state,ds.pos));
        }

        while(ds.pos < ds.buffer.length && ds.state!= 13 && null != ds.r){
            switch(ds.state){
                case 0://ST
                    switch(ds.r[0].charAt(0)){
                        case    ','  /*C*/: ds.state = 1; break;
                        case    '\"' /*Q*/: ds.state = 3; break;
                        case    '\r' /*R*/: ds.state = 10;break;  
                        case    '\n' /*F*/: ds.state = 11;break;
                        default      /*T*/: ds.state = 2; break;
                    }
                break;
                case 1://CM - possibly merge with 0
                    switch(ds.r[0].charAt(0)){
                        case    ','  /*C*/: ds.state = 1; break;
                        case    '\"' /*Q*/: ds.state = 3; break;
                        case    '\r' /*R*/: ds.state = 10;break;  
                        case    '\n' /*F*/: ds.state = 11;break;
                        default      /*T*/: ds.state = 2; break;
                    }
                break;
                case 2://NE 
                    switch(ds.r[0].charAt(0)){
                        case    ','  /*C*/: ds.state = 1; break;
                        case    '\r' /*R*/: ds.state = 10;break;  
                        case    '\n' /*F*/: ds.state = 11;break;
                        default      /*QT*/: ds.state = 13; break;
                    }
                break;
                case 3://ES
                    switch(ds.r[0].charAt(0)){
                        case    ','  /* C*/: ds.state = 1; break;
                        case    '\r' /* R*/: ds.state = 10; break;  
                        case    '\n' /* F*/: ds.state = 11; break;
                        default      /*QT*/: ds.state = 13; break;
                    }
                break;
                case 10://CR 
                    switch(ds.r[0].charAt(0)){
                        case    ','  /*C*/: ds.state = 13;break;
                        case    '\"' /*Q*/: ds.state = 13;break;
                        case    '\r' /*R*/: ds.state = 13;break;  
                        case    '\n' /*F*/: ds.state = 11;break;
                        default      /*T*/: ds.state = 13;break;
                    }
                break;
                case 11://LF 
                    switch(ds.r[0].charAt(0)){
                        case    ','  /*C*/: ds.state = 1; break;
                        case    '\"' /*Q*/: ds.state = 3; break;
                        case    '\r' /*R*/: ds.state = 10;break;  
                        case    '\n' /*F*/: ds.state = 11;break;
                        default      /*T*/: ds.state = 2; break;
                    }
                break;
            }
            switch(ds.state){
                //  ST      CM      ES       CR       LF       XX
                case 0: case 1: case 3: case 10: case 11: //case 13: 
                    (SCB&&SCB(ds,ds.state,ds.pos)); break;
                //  NE
                case 2: 
                    (SCB&&SCB(ds,ds.state,ds.pos));break;
                //   XX
                case 13: 
                    (SCB&&SCB(ds,ds.state,ds.pos)); break;
            }
            ds.pos = ds.S.lastIndex;
            ds.prev = ds.state;
            ds.r = ds.S.exec(ds.buffer);
            if(null == ds.r && ds.pos < ds.buffer.length-1){ds.state==13;}
            if(null != ds.r && ds.r.index != ds.pos){
                ds.state = 13;
                (SCB&&SCB(ds,ds.state,ds.pos));
            }
        }
    }

    function closeBuffer(ds,SCB){
        if (ds.pos == ds.buffer.length && ds.state != 13) {
            ds.state = 12;
            switch(ds.prev){
                case 0: case 1: case 2: case 3: case 11:
                    (SCB&&SCB(ds,ds.state,ds.pos));
                    break;
                default : 
                    ds.prev = ds.state; ds.state = 13;   
                    (SCB&&SCB(ds,ds.state,ds.pos));
                    break;
            }
        }
    }

    function onToken(ds, state, pos, end){
        var DQDQ = /""/g;
        switch(state) {
            case  1 : //CM 
                switch(ds.prevState){
                    case 0: case 1: case 11: //ST,CM,CR
                        ds.record.push("");
                    break;
                    case 2: //NE
                        ds.record.push(ds.buffer.slice(ds.prevPos,pos));
                    break;
                    case 3: //ES
                        ds.record.push(ds.buffer.slice(ds.prevPos+1,pos-1).replace(DQDQ,"\""));
                    break;
                }
                break;
            case 10 : //CR 
                switch(ds.prevState){
                    case 0: case 1: //case 11: //ST,CM,LF
                        ds.record.push("");
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                    case 2: //NE
                        ds.record.push(ds.buffer.slice(ds.prevPos,pos));
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                    case 3: //ES
                        ds.record.push(ds.buffer.slice(ds.prevPos+1,pos-1).replace(DQDQ,"\""));
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                }
                break;
            case 11 : //LF 
                switch(ds.prevState){
                    case 0: case 1: case 11: //ST,CM,CR
                        ds.record.push("");
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                    case 2: //NE
                        ds.record.push(ds.buffer.slice(ds.prevPos,pos));
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                    case 3: //ES
                        ds.record.push(ds.buffer.slice(ds.prevPos+1,pos-1).replace(DQDQ,"\""));
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                }
                break;
            case 12 : //FN 
                switch(ds.prevState){
                    case 0: case 1: //ST,CM
                        ds.record.push("");
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                    case 2: //NE
                        ds.record.push(ds.buffer.slice(ds.prevPos,pos));
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                    case 3: //ES
                        ds.record.push(ds.buffer.slice(ds.prevPos+1,pos-1).replace(DQDQ,"\""));
                        ds.data.push(ds.record); ds.record = []; 
                    break;
                    case 11: //CR
                    break;
                }
                break;
        }
        ds.prevState = state;
        ds.prevPos = pos;
    }

    function init(){
         return { data:[], record:[], S:/(?:,|\n|\r|[^,\"\n\r]+|\"(\"\"|[^\"]+)*\")/g }; 
         //return { data:[], record:[], S:/([^,\"\n\r]+|\"(\"\"|[^\"]+)*\")/g }; 
    }
    
    function parse(buffer,SCB){
        var ds = init();
        parseBuffer(ds,buffer,SCB);
        closeBuffer(ds,SCB);
    }

    function toArrays(buffer,onComplete){
        var ds = init();
        if(0 == buffer.length){parseBuffer(ds,"",onToken);}
        for(var i=0 ; i < buffer.length; i+=max){
            parseBuffer(ds, buffer.slice(i,(i+max < buffer.length)?i+max:buffer.length), onToken);
        }
        closeBuffer(ds, onToken);
        if(onComplete){onComplete(ds.data);}
        else {return ds.data;}
    }

    nCSV = function (onComplete,onData){ 
        if ( !(this instanceof nCSV) ) { return new nCSV(); }
        this.ds = init(); 
        this.max = 1024*100;
        if(onComplete){this.onComplete = onComplete};
        if(onData){this.onData = onData};
    }

    nCSV.prototype.write = function(buffer){
        if(0 == buffer.length){parseBuffer(ds,"",onToken);}
        for(var i=0 ; i < buffer.length; i+=this.max){
            parseBuffer(this.ds, buffer.slice(i,(i+this.max < buffer.length)?i+this.max:buffer.length), onToken);
            if(this.onData){this.onData(this.ds.data);}
        }
    }

    nCSV.prototype.writeAll = function(buffer){
        this.write(buffer);
        return this.close();
    }

    nCSV.prototype.close = function(){
        closeBuffer(this.ds, onToken);
        if(this.onComplete){onComplete(this.ds.data);}
        else {return this.ds.data;}
    }

    nCSV.init = init;
    nCSV.onToken = onToken;
    nCSV.parseBuffer = parseBuffer;
    nCSV.closeBuffer = closeBuffer;
    nCSV.parse = parse;
    nCSV.toArrays = toArrays;
    nCSV.stateNames = stateNames;

    if('object' == typeof exports){
        module.exports = nCSV;
    }

})();

